// Copyright 2013 Ardan Studios. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package mongo provides mongo connectivity support.
package mongo

import (
	"encoding/json"
	//	"fmt"
	log "github.com/goinggo/tracelog"
	"github.com/kelseyhightower/envconfig"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	//	mgolog "log"
	//	"os"
	//	"encoding/gob"
	"strings"
	"time"
)

const DbName = "mgo"
const sessionID = "mgo"

var (
	// Reference to the singleton.
	singletonSession *mgo.Session
	dbInitcbs        map[string]func()
)

type (
	// mongoConfiguration contains settings for initialization.
	mongoConfiguration struct {
		Hosts    string
		Database string
		UserName string
		Password string
	}
)

// Startup brings the manager to a running state.
func RegInitCb(cbname string, cb func()) error {
	if dbInitcbs == nil {
		dbInitcbs = make(map[string]func())
	}
	dbInitcbs[cbname] = cb
	return nil
}

func init() {

	//	gob.RegisterName(".gopkg.in/mgo.v2/bson.ObjectId", bson.ObjectId)
}

// Startup brings the manager to a running state.
func Startup() error {
	// If the system has already been started ignore the call.
	if singletonSession != nil {
		return nil
	}
	mgo.SetDebug(true)
	//mgo.SetLogger(mgolog.New(os.Stdout, "MGOLOG: ", mgolog.Ltime|mgolog.Lshortfile))
	log.Started(sessionID, "Startup")

	// Pull in the configuration.
	var config mongoConfiguration
	if err := envconfig.Process("mgo", &config); err != nil {
		log.CompletedError(err, sessionID, "Startup")
		return err
	}

	// Log the mongodb connection straps.
	log.Trace(sessionID, "Startup", "MongoDB : Hosts[%s]", config.Hosts)
	log.Trace(sessionID, "Startup", "MongoDB : Database[%s]", config.Database)
	log.Trace(sessionID, "Startup", "MongoDB : Username[%s]", config.UserName)

	config.Hosts = "localhost"
	config.Database = DbName
	config.UserName = "mgo"
	config.Password = "mgo"

	hosts := strings.Split(config.Hosts, ",")

	// Create the strong session.
	if err := CreateSession(hosts, config.Database, config.UserName, config.Password); err != nil {
		log.CompletedError(err, sessionID, "Startup")
		return err
	}

	log.Completed(sessionID, "Startup and call initdb callback")

	for key, f := range dbInitcbs {
		log.Trace(sessionID, "Startup", key)
		f()
	}
	return nil
}

// Shutdown systematically brings the manager down gracefully.
func Shutdown(sessionID string) error {
	log.Started(sessionID, "Shutdown")

	singletonSession.Close()

	log.Completed(sessionID, "Shutdown")
	return nil
}

// CreateSession creates a connection pool for use.
func CreateSession(hosts []string, databaseName string, username string, password string) error {

	// Create the database object
	mongoSession := &mgo.DialInfo{
		Addrs:    hosts,
		Timeout:  60 * time.Second,
		Database: databaseName,
		Username: username,
		Password: password,
	}

	// Establish the master session.
	var err error
	singletonSession, err = mgo.DialWithInfo(mongoSession)
	if err != nil {
		log.CompletedError(err, sessionID, "CreateSession")
		return err
	}
	singletonSession.SetMode(mgo.Strong, true)

	// Have the session check for errors.
	// http://godoc.org/github.com/finapps/mgo#Session.SetSafe
	singletonSession.SetSafe(&mgo.Safe{})

	log.Completed(sessionID, "CreateSession")
	return nil
}

// GetCollection returns a reference to a collection for the specified database and collection name.
func GetDB() *mgo.Database {
	if singletonSession == nil {
		return nil
	}
	return singletonSession.DB(DbName)
}

// GetCollection returns a reference to a collection for the specified database and collection name.
func GetCollection(useCollection string) *mgo.Collection {
	if singletonSession == nil {
		return nil
	}
	return singletonSession.DB(DbName).C(useCollection)
}

// CollectionExists returns true if the collection name exists in the specified database.
func CollectionExists(useDatabase string, useCollection string) bool {
	database := singletonSession.DB(DbName)
	collections, err := database.CollectionNames()

	if err != nil {
		return false
	}

	for _, collection := range collections {
		if collection == useCollection {
			return true
		}
	}

	return false
}

// ToString converts the quer map to a string.
func ToString(queryMap interface{}) string {
	json, err := json.Marshal(queryMap)
	if err != nil {
		return ""
	}

	return string(json)
}

// ToStringD converts bson.D to a string.
func ToStringD(queryMap bson.D) string {
	json, err := json.Marshal(queryMap)
	if err != nil {
		return ""
	}

	return string(json)
}
